﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.Modeling.Validation;
using System.Windows.Data;
using System.Reflection;
using Microsoft.VisualStudio.Modeling;
using System.Collections;
using Microsoft.VisualStudio.Modeling.Diagrams;
using CodeDomHelper.Types;
using devtm.Editor.TypeSelector;
using System.Text.RegularExpressions;
using System.Diagnostics;
using devtm.Editor.TypeSelector.Assemblies;
using VSLangProj;
using System.IO;
using devtm.Editor.TypeSelector.Dsl2Dte;
using Microsoft.RestrictedUsage.CSharp.Semantics;
using CustomTypes.Mono.Extensions;
using CustomTypes.Interfaces;
using Mono.Cecil;
using EnvDTE;
using CustomTypes.Mono.Serializers;



namespace devtm.AutoMapper
{

    [ValidationState(ValidationState.Enabled)]
    public partial class Mapper
    {


        private const string Pattern1 = @"[^\w\d]*";

        [ThreadStatic]
        private static Hashtable _MethodList = null;
        private static Stopwatch _time = new Stopwatch();
        private static Regex regExCsharpName = new Regex(Pattern1, RegexOptions.Compiled);
        private static bool StopTest;
        private static bool inError = false;
        public string ItemTested;
        private static string[] tbNumeric = new string[] { "System.Int16", "System.Int32", "System.Int64", "System.UInt16", "System.UInt32", "System.UInt64" };

        [ThreadStatic]
        static CustomTypeRepository repository;

        TypeExpression typeExpressionSource = null;
        TypeDefinition TypeDefinitionSource = null;
        TypeExpression typeExpressionTarget = null;
        TypeDefinition TypeDefinitionTarget = null;


        [ValidationMethod(ValidationCategories.Open | ValidationCategories.Save)]
        private void ValidateAttributeNameAsValidIdentifier(ValidationContext context)
        {

            #region Value Source and target

            if (MappingSource == null || MappingSource.TypeElement == null)
            {
                context.LogError(string.Format("the mapper {0} must have a source type", this.Name), "1", this);
                return;
            }

            if (MappingDestination == null || MappingDestination.TypeElement == null)
            {
                context.LogError(string.Format("the mapper {0} must have a target type", this.Name), "1", this);
                return;
            }

            #endregion


            try
            {


                if (repository == null || this.MappingModel.Mappers.FirstOrDefault() == this)
                {
                    _MethodList = new Hashtable();
                    _time.Reset();
                    _time.Start();
                    repository = CustomTypeRepository.Instance(this.Store, true);
                    if (repository.ContainingProject == null)
                    {
                        repository = null;
                        return;
                    }
                }


                CheckAmbiguiousMapMethod(context, this);


                #region Find Type

                // On cherche les types dans les assemblies
                typeExpressionSource = TypeExpression.Analyse(this.TypeSource);
                TypeDefinitionSource = repository.GetType(typeExpressionSource);
                typeExpressionTarget = TypeExpression.Analyse(this.TypeDestination);
                TypeDefinitionTarget = repository.GetType(typeExpressionTarget);

                if (TypeDefinitionSource == null)
                {
                    string assName = typeExpressionSource.AssemblyName;
                    if (!repository.Contains(assName))
                        context.LogError("Assembly name from source can't be resolved", "1", this.MappingSource);
                    else
                        context.LogError(string.Format("the type source name '{0}' can't be found", typeExpressionSource.FullName), "1", this.MappingSource);
                }

                if (TypeDefinitionTarget == null)
                {
                    string assName = typeExpressionTarget.AssemblyName;
                    if (!repository.Contains(assName))
                        context.LogError("Assembly name from target can't be resolved", "1", this.MappingDestination);
                    else
                        context.LogError(string.Format("the type target name '{0}' can't be found", typeExpressionTarget.FullName), "1", this.MappingDestination);
                }

                if (TypeDefinitionSource == null || TypeDefinitionTarget == null)
                    return;

                #endregion

                SetTypeElementProperties(this.MappingSource.TypeElement, context, TypeDefinitionSource);
                SetTypeElementProperties(this.MappingDestination.TypeElement, context, TypeDefinitionTarget);

                using (Transaction t = Store.TransactionManager.BeginTransaction("Update Property"))
                {

                    if (this.MappingModel.Mappers.FirstOrDefault() == this)
                    {
                        inError = false;
                        ReferenceTypeProvider.CashAssemblies.Clear();
                        StHostingProject.CashAssemblies.Clear();
                        SetRecursifOff();
                        Mapper.StopTest = false;
                        ContainerTypes.PutInCache(ContainerTypes.GetTypes(this.Store, null).ToList());
                    }

                    if (inError)
                        return;

                    ItemTested = string.Empty;
                    CheckRecursifProperties(this, new Stack<Mapper>());

                    t.Commit();

                }

                ItemTested = string.Empty;

                if (StopTest)
                    return;


                // tester si les noms de methodes avec des parametres ne se chevauchent pas.

                if ((this.MappingDestination.TypeElement.IsAbstract || this.MappingDestination.TypeElement.IsInterface) && !this.CustomConstructor)
                    context.LogError("the target type is abstract or interface type. you must activate the CustomCreator switch", "1", this);

                if (!CheckProperties(context))
                    return;

                if (!this.CustomConstructor)
                {

                    TypeElement ee = this.MappingDestination.TypeElement;

                    if (ee.IsAbstract || ee.IsInterface)
                        context.LogError("the target type is abstract type, yous must set to enable the Custom Constructor parameter", "2", this);
                }


            }
            catch (AssemblyResolutionException e1)
            {
                context.LogError(e1.Message + ". Please add a reference.", "Validation error");
            }
            catch (Exception e)
            {

                StackTrace trace = new StackTrace(e, true);
                string msg = "Exception throwed on mapper '" + this.Name + "'";

                if (!string.IsNullOrEmpty(ItemTested))
                    msg += " at item " + ItemTested;

                foreach (System.Diagnostics.StackFrame item in trace.GetFrames())
                    msg += " method " + item.GetMethod().ToString() + " offset : " + item.GetNativeOffset().ToString() + "\n";

                context.LogError(msg, "Validation error");

                throw;

            }
            finally
            {



                ItemTested = string.Empty;

                typeExpressionSource = null;
                TypeDefinitionSource = null;
                typeExpressionTarget = null;
                TypeDefinitionTarget = null;

                
                    if (this.MappingModel.Mappers.LastOrDefault() == this)
                    {

                        #region  Test the class name

                        if (this.MappingModel.ContainsPool)
                        {
                            var models = Store2DTE.GetOtherModels(Store).ToList();
                            foreach (var item in models)
                                using (Store store = StoreHelper.GetStore(item))
                                {
                                    var e = store.ElementDirectory;
                                    if (e != null)
                                    {
                                        MappingModel f = e.AllElements.FirstOrDefault() as MappingModel;
                                        if (f != null)
                                        {
                                            if (f.ClassName == this.MappingModel.ClassName)
                                            {

                                                string filename = Path.GetFileName(f.FilePath);

                                                string txt = string.Format("Class conflict name with {0}. the generated code from this model contains pool of indexed target. the name of the class na must be unique.", filename);

                                                context.LogError(txt, "1", this.MappingModel);
                                                
                                            }
                                        }
                                    }
                                }
                        }

                        #endregion

                        #region clear the repository

                        if (repository != null)
                        {
                            _MethodList = null;
                            repository.Dispose();
                            repository = null;
                            _time.Stop();
                            devtm.AutoMapper.Helpers._output.WriteLine(string.Format("Validation in {0}", _time.Elapsed));
                        }
                    
                        #endregion

                    }
                
            }

        }

        private void CheckAmbiguiousMapMethod(ValidationContext context, Mapper map)
        {

            if (map == null || map.MappingModel == null || map.MappingSource == null || map.MappingSource.TypeElement == null)
                return;

            string key = string.Format("{0} {1} {2}",map.MappingModel.ClassName, map.MappingSource.TypeElement.FullName, map.MapMethodName);
            if (_MethodList.ContainsKey(key))
            {

                string[] mapper = _MethodList[key] as string[];
                string _er1 = string.Format("Ambiguous method name. The map method name '{0}' from mapper {1} has same method sign from {2} mapper {3}", map.MapMethodName, map.Name, mapper[0], mapper[1]);
                context.LogError(_er1, "3", map);
            }
            else
            {


                string docName = string.Empty;

                if (map.MappingModel.ClassName != this.MappingModel.ClassName)
                {
                    docName = map.MappingModel.ClassName;

                    IFilePath f = map.Store as IFilePath;
                    if (f != null)
                    {
                        if (!string.IsNullOrEmpty(f.FilePath))
                            docName = Path.GetFileName(f.FilePath);
                    }

                }

                _MethodList.Add(key, new string[] { docName, map.Name });

            }

        }

        private HashSet<string> GetHashsetList(CSharpType type)
        {

            HashSet<string> lst = new HashSet<string>();

            foreach (CSharpMember item in type.Members)
                lst.Add(item.Name.Text);

            return lst;

        }      


        #region Recursif
        
        private void CheckRecursifProperties(Mapper m, Stack<Mapper> queue)
        {

            queue.Push(m);

            foreach (ItemMap item in m.ItemMaps)
            {


                if (!item.Enable)
                    continue;


                this.ItemTested = item.Label;
                if (!(item.SourceIsClass || item.SourceIsInterface) || item.SourceType == "System.String" || item.IsRecursif || !string.IsNullOrEmpty(item.ExternalModelDocument))
                    continue;


                string type = string.Empty;

                if (item.SourceIsEnumerable | item.SourceIsArray)
                {
                    TypeExpression t = BuildTypeExpression.Analyse(item.SourceType);
                    type = t.IsGeneric
                        ? t.SubType.AssemblyQualifiedName.Replace("[]", "")
                        : t.AssemblyQualifiedName.Replace("[]", "");
                }
                else
                    type = item.SourceType;


                switch (item.TypeCast)
                {

                    case CastType.Map:
                    case CastType.Collection:
                    case CastType.List:
                    case CastType.ObservableCollection:
                    case CastType.Array:

                        Mapper m2 = this.MappingModel.FindMapperFor(item);
                        if (m2 != null)
                        {

                            if (queue.Contains(m2))
                            {
                                item.IsRecursif = true;
                                this.MappingModel.ContainsPool = true;
                                continue;
                            }

                            if (!item.IsRecursif)
                            {
                                CheckRecursifProperties(m2, queue);
                                //CheckRecursifFields(m2, queue);
                            }

                        }
                        else
                        {

                        }

                        break;



                    default:
                        break;

                }
            }

            queue.Pop();
        }

        /*
        private void CheckRecursifFields(Mapper m, Stack<Mapper> queue)
        {

            queue.Push(m);

            foreach (ItemMap item in m.ItemMaps)
            {

                this.ItemTested = item.Label;

                if (!item.Enable || string.IsNullOrEmpty(item.SourceItemType) || string.IsNullOrEmpty(item.SourceName) || string.IsNullOrEmpty(item.TargetItemType) || string.IsNullOrEmpty(item.TargetName))
                    continue;

                this.ItemTested = item.Label;
                if (!(item.SourceIsClass || item.SourceIsInterface) || item.SourceType == "System.String" || item.IsRecursif || !string.IsNullOrEmpty(item.ExternalModelDocument))
                    continue;

                string type = string.Empty;

                if (item.SourceIsEnumerable)
                {
                    TypeExpression t = BuildTypeExpression.Analyse(item.SourceType);
                    type = t.IsGeneric
                        ? t.SubType.AssemblyQualifiedName.Replace("[]", "")
                        : t.AssemblyQualifiedName.Replace("[]", "");
                }
                else
                    type = item.SourceType;

                Mapper m2 = this.MappingModel.FindMapperFor(type);
                if (m2 != null)
                {
                    if (queue.Contains(m2))
                    {
                        item.IsRecursif = true;
                        continue;
                    }

                    if (!item.IsRecursif)
                    {
                        CheckRecursifProperties(m2, queue);
                        CheckRecursifFields(m2, queue);
                    }
                }
            }

            queue.Pop();

        }
        */

        private void SetRecursifOff()
        {

            this.MappingModel.ContainsPool = false;

            foreach (Mapper m in this.MappingModel.Mappers)
            {

                foreach (ItemMap p in m.ItemMaps)
                {
                    if (p.IsRecursif)
                        p.IsRecursif = false;
                }

            }

        }

        #endregion


        private void SetTypeElementProperties(TypeElement e, ValidationContext context, TypeDefinition typeDefinition)
        {

            bool test = false;

            Action<object, System.ComponentModel.PropertyChangedEventArgs> act = (a, b) =>
            {
                test = true;
            };

            System.ComponentModel.PropertyChangedEventHandler action = new System.ComponentModel.PropertyChangedEventHandler(act);

            try
            {

                e.PropertyChanged += action;

                using (Transaction t = Store.TransactionManager.BeginTransaction("Update TypeElement"))
                {

                    e.Namespace = typeDefinition.Namespace;
                    e.FullName = typeDefinition.FullName;
                    e.IsAbstract = typeDefinition.IsAbstract;
                    e.IsAnsiClass = typeDefinition.IsAnsiClass;
                    e.IsArray = typeDefinition.IsArray;
                    e.IsClass = typeDefinition.IsClass;
                    e.IsEnum = typeDefinition.IsEnum;
                    e.IsInterface = typeDefinition.IsInterface;
                    e.IsNested = typeDefinition.IsNested;
                    e.IsPublic = typeDefinition.IsPublic;
                    e.IsSealed = typeDefinition.IsSealed;
                    e.IsValueType = typeDefinition.IsValueType;
                    e.IsSerializable = typeDefinition.IsSerializable;
                    e.IsEnumerable = typeDefinition.GetInterface("IEnumerable", true) != null;

                    if (e.IsEnumerable)
                    {

                        TypeReference typeEnumerable = MappingHelper.FindItemFromIEnumerable(typeDefinition);

                        var ee = e.ItemType;

                        if (typeEnumerable != null)
                            e.ItemType = typeEnumerable.AssemblyQualifiedName;

                        else
                            e.ItemType = string.Empty;

                        test = test | (ee != e.ItemType);

                    }

                    e.Namespace = typeDefinition.Namespace;
                    e.FullName = typeDefinition.FullName;
                    e.Name = typeDefinition.Name;

                    if (test)
                        t.Commit();

                }
            }
            finally
            {
                e.PropertyChanged -= action;
            }
        }
        


        #region ItemMap

        #region Valide format des properties

        /// <summary>
        /// Suit le fils des membres
        /// </summary>
        /// <param name="context"></param>
        /// <param name="item"></param>
        /// <param name="itemName"></param>
        /// <param name="itemType"></param>
        /// <param name="source"></param>
        private bool TestItemMap(ValidationContext context, ItemMap item, string itemName, string itemType, bool source, TypeDefinition type, TypeElement typeElement)
        {

            MemberInfoDesc p1 = ResolveProperty(type, itemName).LastOrDefault(); 

            string wayStr = source ? "source" : "target";  

            if (p1 == null)
            {
                string assemblyName = TypeExpression.Analyse(typeElement.Name).AssemblyQualifiedNameUltraShort;
                context.LogError(string.Format("the {2} {0} is not resolvable. Please check the {2} assembly {1}", item.Mapper.MappingSource.TypeElement.Name, assemblyName, wayStr), "1", item);
                Mapper.StopTest = true;

                return false;
            }
            else if (p1.Member == null)
            {
                context.LogError(string.Format("mismatch on the {1} name '{0}'.", p1.PropertyName, wayStr), "1", item);
                return false;
            }

            if (!source)
            {
                if (item.TypeCast != CastType.Custom && p1.Member != null)
                {

                    PropertyReference pr = p1.Member as PropertyReference;
                    if (pr != null)
                    {
                        var pr2 = pr.Resolve();
                        if (!pr2.CanReadPublic)
                        {
                            context.LogError("the target is not readable. Please check the public get accessor", "1", item);
                            return false;
                        }
                    }

                }
            }
            else
            {
                PropertyReference pr = p1.Member as PropertyReference;
                if (pr != null)
                {
                    var pr2 = pr.Resolve();
                    if (!pr2.CanWrite)
                    {
                        context.LogError("the target is not writable. Please check the public set accessor", "1", item);
                        return false;
                    }
                }
            }

            TestMember(context, item, itemName, itemType, source, p1);

            if (!string.IsNullOrEmpty(item.CustomMethodName))            
                if (!regExCsharpName.IsMatch(item.CustomMethodName))
                {
                    context.LogError("the custom class method name is not a valid name", "1", item);
                    return false;
                }
                
            

            return true;

        }

        [CLSCompliant(false)]
        public class MemberInfoDesc
        {
            public TypeDefinition Type { get; set; }
            public MemberReference Member { get; set; }
            public string PropertyName { get; set; }

            public MemberTypeEnum MemberType
            {
            get
            {

                if (Member.IsProperty())
                    return MemberTypeEnum.Property;

                if (Member.IsField())
                    return MemberTypeEnum.Field;

                return MemberTypeEnum.Undefine;

            }
        }

        }

        private IEnumerable<MemberInfoDesc> ResolveProperty(TypeDefinition type, string propertyName)
        {

            if (type == null)
                yield break;

            if (propertyName == "this")
                yield return new MemberInfoDesc() { Type = type, Member = type, PropertyName = propertyName };

            else
            {

                string[] tb = propertyName.Split('.');
                TypeReference ttt = type;

                foreach (string name in tb)
                {

                    MemberReference[] tt = ttt.GetMembers(name).ToArray();

                    if (tt == null || tt.Length == 0)
                    {
                        yield return new MemberInfoDesc() { PropertyName = name };
                        break;
                    }

                    MemberInfoDesc m = GetMemberInfoDesc(ref ttt, name, tt);
                    m.Type = type;

                    if (m != null)
                        yield return m;

                }

            }
        }


        private MemberInfoDesc GetMemberInfoDesc(ref TypeReference ttt, string name, MemberReference[] members1)
        {

            TypeDefinition ty = ttt.Resolve();

            if (ty != null)
            {

                var members = ty.GetMembers(name);

                foreach (MemberReference m in members)
                {

                    switch (m.MemberType)
                    {

                        case MemberTypes.Field:
                        case MemberTypes.Property:
                            ttt = m.GetTypeFrom(ttt);
                            return new MemberInfoDesc() { Member = m, PropertyName = m.Name };

                        case MemberTypes.Method:
                            ParameterDefinition[] ps = (m as MethodDefinition).Parameters.ToArray();
                            if (ps.Count() > 0)
                                continue;
                            ttt = m.GetTypeFrom(ttt);
                            return new MemberInfoDesc() { Member = m, PropertyName = m.Name };

                        case MemberTypes.Constructor:
                        case MemberTypes.Custom:
                        case MemberTypes.Event:
                        case MemberTypes.NestedType:
                        case MemberTypes.TypeInfo:
                        default:
                            break;

                    }

                }

            }
            return null;

        }


        private void TestMember(ValidationContext context, ItemMap item, string propertyName, string propertyType, bool source, MemberInfoDesc prop)
        {            

            TypeReference type1 = prop.Member.GetTypeFrom(prop.Type);    
            TypeDefinition type = type1.Resolve();
            bool test = false;

            Action<object, System.ComponentModel.PropertyChangedEventArgs> act = (a, b) =>
            {
                test = true;
            };

            System.ComponentModel.PropertyChangedEventHandler action = new System.ComponentModel.PropertyChangedEventHandler(act);

            try
            {

                item.PropertyChanged += action;

                using (Transaction t = Store.TransactionManager.BeginTransaction("Update Property"))
                {

                    string tpe = TypeDefinitionHelper.FindTypeName(prop.Member, prop.Type);

                    if (source)
                    {

                        item.SourceType = tpe;
                        item.SourceTypeMember = prop.MemberType;
                        item.SourceIsAbstract = type.IsAbstract;
                        item.SourceIsAnsiClass = type.IsAnsiClass;
                        item.SourceIsArray = type1.IsArray;  // Ce n'est pas une erreur
                        if (type1.IsArray)
                            item.SourceItemType = type.AssemblyQualifiedName;
                        item.SourceIsClass = type.IsClass;
                        item.SourceIsEnum = type.IsEnum;
                        item.SourceIsInterface = type.IsInterface;
                        item.SourceIsNested = type.IsNested;
                        item.SourceIsPublic = type.IsPublic;
                        item.SourceIsSealed = type.IsSealed;
                        item.SourceIsValueType = type.IsValueType;
                        item.SourceIsSerializable = type.IsSerializable;
                        item.SourceIsEnumerable = type.IsEnumerable;
                        if (item.SourceIsEnumerable)
                        {
                            TypeReference typeEnumerable = MappingHelper.FindItemFromIEnumerable(type1);

                            if (typeEnumerable != null)
                                item.SourceItemType = typeEnumerable.AssemblyQualifiedName;

                            else
                                item.SourceItemType = string.Empty;

                        }

                        item.SourceIsNullable = type.AssemblyQualifiedName.StartsWith("System.Nullable`1[[");

                        item.SourceProperties = GetProperties(type);

                    }
                    else
                    {
                        item.TargetType = tpe;

                        item.TargetTypeMember = prop.MemberType;

                        item.TargetIsAbstract = type.IsAbstract;
                        item.TargetIsAnsiClass = type.IsAnsiClass;
                        item.TargetIsArray = type1.IsArray; // Ce n'est pas une erreur
                        if (type1.IsArray)
                            item.TargetItemType = type.AssemblyQualifiedName;
                        item.TargetIsClass = type.IsClass;
                        item.TargetIsEnum = type.IsEnum;
                        item.TargetIsInterface = type.IsInterface;
                        item.TargetIsNested = type.IsNested;
                        item.TargetIsPublic = type.IsPublic;
                        item.TargetIsSealed = type.IsSealed;
                        item.TargetIsValueType = type.IsValueType;
                        item.TargetIsSerializable = type.IsSerializable;
                        item.TargetIsEnumerable = type1.GetInterface("IEnumerable", true) != null;
                        if (item.TargetIsEnumerable)
                        {

                            TypeReference typeEnumerable = MappingHelper.FindItemFromIEnumerable(type1);

                            if (typeEnumerable != null)
                                item.TargetItemType = typeEnumerable.AssemblyQualifiedName;

                            else
                                item.TargetItemType = string.Empty;

                        }
                        item.TargetIsNullable = type.AssemblyQualifiedName.StartsWith("System.Nullable`1[[");
                        item.TargetProperties = GetProperties(type);
                    }

                    if (test)
                        t.Commit();
                }


            }
            catch (Exception)
            {

                throw;
            }
            finally
            {
                item.PropertyChanged -= action;
            }
        }

        private string GetProperties(TypeDefinition typeDefinition)
        {

            string result = string.Empty;

            if (typeDefinition.IsEnum)
            {


                EnumSerializer s = new EnumSerializer() { Name = typeDefinition.Name };
                if (typeDefinition.DeclaringType != null)
                {
                    s.Namespace = typeDefinition.DeclaringType.Namespace;
                    s.DeclaringType = typeDefinition.DeclaringType.Name;
                }
                else
                {
                    s.Namespace = typeDefinition.Namespace;
                    s.DeclaringType = string.Empty;
                }

                foreach (FieldDefinition item in typeDefinition.GetMembers(MemberTypes.Field))
                    if (item.Name != "value__")
                        s.Items.Add(new FieldSerializer() { Constant = item.Constant.ToString(), Name = item.Name });

                result = SerialiserBase.Serialize(s);

            }

            return result;
        }

        #endregion

        #region Test les cast

        private bool CheckProperties(ValidationContext context)
        {

            if (context.Categories != ValidationCategories.Save)
                return true;

            if (this.ReferenceMultiple)
                this.MappingModel.ContainsPool = true;

            ItemTested = string.Empty;
            foreach (ItemMap item in this.ItemMaps)
            {

              
                this.ItemTested = item.Label;

                if (string.IsNullOrEmpty(item.SourceName) || string.IsNullOrEmpty(item.TargetName))
                {
                    using (Transaction t = Store.TransactionManager.BeginTransaction("Disable ItemMap from " + item.Mapper.Name))
                    {
                        item.Enable = false;
                        t.Commit();
                    }

                    continue;
                }

                // || string.IsNullOrEmpty(item.SourceItemType) || string.IsNullOrEmpty(item.TargetItemType)
                if (!item.Enable || string.IsNullOrEmpty(item.SourceName) || string.IsNullOrEmpty(item.TargetName))
                    continue;

                if (!TestItemMap(context, item, item.SourceName, item.SourceType, true, TypeDefinitionSource, this.MappingSource.TypeElement))
                    return false;

                if (!TestItemMap(context, item, item.TargetName, item.TargetType, false, TypeDefinitionTarget, this.MappingDestination.TypeElement))
                    return false;

                // Gerer les types abstract

                // revoir les commentaires dans le code.


                if (item.TargetIsAbstract || item.TargetIsInterface)
                    if (item.TargetIsEnumerable)
                    {
                        if (
                             item.TypeCast != CastType.Array && 
                             item.TypeCast != CastType.Collection && 
                             item.TypeCast != CastType.Hashset && 
                             item.TypeCast != CastType.List && 
                             item.TypeCast != CastType.ObservableCollection
                           )
                        {
                            context.LogError("the target is an abstract IEnumerable. the cast way must be the target is an enumerable. the cast way must be (Array List, Collection, Hashset, ObservableCollection or Custom)", "2", item);
                        }
                    }
                


                if (item.SourceType != item.TargetType)
                    CheckItemMap(context, item);

            }

            ItemTested = string.Empty;
            return true;

        }

        private void CheckItemMap(ValidationContext context, ItemMap item)
        {

            if (item.TypeCast == CastType.Map)
                    ValidateMap(context, item);

            else if (!item.NeedMap)
                TestItemMapWithCast(context, item);
            
            else
            {

                if (item.TypeCast != CastType.Custom)
                {

                    // tester la validation des class et des enumerables
                    if (!item.TargetIsEnumerable)
                    {
                        if (item.TypeCast == CastType.List || item.TypeCast == CastType.Collection || item.TypeCast == CastType.Hashset || item.TypeCast == CastType.ObservableCollection)
                            context.LogError("the target is not enumerable. the cast way can't be (List, Collection, Hashset or ObservableCollection)", "2", item);
                    }
                    else
                    {
                        if (item.TargetType != "System.String")
                            if (item.TypeCast != CastType.List && item.TypeCast != CastType.Collection && item.TypeCast != CastType.Hashset && item.TypeCast != CastType.ObservableCollection)
                                context.LogError("the target is an enumerable. the cast way must be (None, List, Collection, Hashset, ObservableCollection, Custom)", "2", item);
                    }

                    if (item.TargetIsArray && item.TypeCast != CastType.Array)
                        context.LogError("the target is an array. the cast way must be none (for copy reference), Array or Custom", "2", item);

                    if (item.SourceName == "this")
                        context.LogError("the target is this. the cast way must be Custom", "2", item);

                    if (item.TargetIsClass && 
                        !item.TargetIsArray && 
                        !item.TargetIsEnumerable && 
                        !IsSimpleValue(item.TargetType) && 
                        item.TypeCast != CastType.Map)
                        context.LogError("the target is a class. the cast way must be Map or Custom", "2", item);

                }
            }

        }


        private static Type[] types = new Type[] 
            { typeof(string), typeof(sbyte), typeof(byte), typeof(char)
              , typeof(short), typeof(ushort), typeof(int), typeof(uint)
              , typeof(long), typeof(ulong) , typeof(float), typeof(double)
              , typeof(Int16), typeof(Int32), typeof(Int64)                
              , typeof(UInt16), typeof(UInt32), typeof(UInt64)
              , typeof(bool), typeof(decimal)
              , typeof(sbyte?), typeof(byte?), typeof(char?)
              , typeof(short?), typeof(ushort?), typeof(int?), typeof(uint?)
              , typeof(long?), typeof(ulong?) , typeof(float?), typeof(double?)
              , typeof(Int16?), typeof(Int32?), typeof(Int64?)                
              , typeof(UInt16?), typeof(UInt32?), typeof(UInt64?)
              , typeof(bool?), typeof(decimal?)
            };


        private static bool IsSimpleValue(string name)
        {

            foreach (Type item in types)
                if (item.FullName == name)
                    return true;

            return false;
        }


        private void TestItemMapWithCast(ValidationContext context, ItemMap item)
        {
            // test pour l'enum

            if (item.SourceIsEnum)
                if (item.TypeCast != CastType.Custom && item.TypeCast != CastType.Explicit && item.TypeCast != CastType.EnumByLabel && item.TypeCast != CastType.EnumByValue)
                    context.LogError("For the property of type Enum the cast must be (Custom, EnumByLabel, EnumByValue or explicit)", "2", item);
            
            switch (item.TypeCast)
            {

                case CastType.IValueConverter:
                    ValidateTypeWithCastIValueconverter(context, item.SourceType, item.TargetType, item.Cast, item);
                    break;

                case CastType.Explicit:
                    if (!item.SourceIsEnum)
                        ValidateTypeWithCastExplicit(context, item);
                    break;

                case CastType.IConverter:
                    //ValidateTypeWithCastIConverter(context, item.SourceType, item.TargetType, item);
                    break;

                case CastType.EnumByLabel:
                    if (!item.SourceIsEnum && item.SourceType != "System.String")
                        context.LogError("with 'EnumByLabel' cast, the property source type must be String or Enum", "3", item);

                    if (!item.TargetIsEnum)
                        context.LogError("the 'EnumByLabel' cast must only be used for enum cast", "3", item);

                    break;

                case CastType.EnumByValue:
                    if (!item.TargetIsEnum && tbNumeric.Contains(item.SourceType))
                        context.LogError("the 'EnumByValue' cast must only be used for enum or numeric type source", "3", item);
                    break;

                case CastType.Implicit:
                case CastType.Custom:
                case CastType.None:
                default:
                    break;

            }
        }


        private void ValidateMap(ValidationContext context, ItemMap item)
        {

            if (item.TargetIsEnumerable && item.TargetType != "System.String")
                context.LogWarning("the target is an enumerable. the cast way must be (List, Collection, Hashset, ObservableCollection)", "2", item);


            if (item.NeedMap && item.TypeCast != CastType.Map)
            {
                context.LogError("the property 'Type Cast' must be selected on Map", "1", item);
            }

            Mapper m = null;


            if (string.IsNullOrEmpty(item.ExternalModelDocument))
            {
                // test dans le model en cours
                m = this.MappingModel.FindMapperFor(item);
                if (m == null)
                {

                    context.LogError(string.Format("Missing mapper for convert '{0} to '{1}'", item.SourceType, item.TargetType), "1", item);

                    if (this.MappingModel.FindTypeElementByClass(item.SourceType) == null)
                        context.LogError(string.Format("Missing container Element for the type '{0}", item.SourceType), "1", item);

                    if (this.MappingModel.FindTypeElementByClass(item.TargetType) == null)
                        context.LogError(string.Format("Missing container Element for the type '{0}", item.TargetType), "1", item);
                }

            }
            else
            {

                m = this.MappingModel.FindExternalMapperFor(item.ExternalModelDocument);
                string[] _select = ((string)item.ExternalModelDocument).Split('!');

                if (m == null)
                {
                    context.LogError(string.Format("Missing mapper {3} in {2} for convert '{0} to '{1}'", item.SourceType, item.TargetType, _select[0], _select[1]), "1", item);
                }
                else
                {

                    var s = CodeDomHelper.Types.BuildTypeExpression.Analyse(m.MappingSource.TypeElement.AssemblyQualifiedName);
                    var t = CodeDomHelper.Types.BuildTypeExpression.Analyse(m.MappingDestination.TypeElement.AssemblyQualifiedName);

                    var s1 = s.IsGeneric
                        ? s.SubType.TypeName
                        : s.TypeName;

                    var t1 = t.IsGeneric
                        ? t.SubType.TypeName
                        : t.TypeName;

                    if (
                        (!s.Equals(item.SourceType) && !s1.Equals(item.SourceType))
                        ||
                        (!t.Equals(item.TargetType) && !t1.Equals(item.TargetType))
                       )
                        context.LogError(string.Format("the mapper {0} in {1} don't convert '{0} to ", item.SourceType, item.TargetType, _select[0], _select[1], s.Name, t.Name), "1", item);


                    CheckAmbiguiousMapMethod(context, m);
                
                }
            }
        }

        /*
        private void ValidateTypeWithCastIConverter(ValidationContext context, string typeSource, string typeTarget, ItemMap item)
        {

            Type filtre = typeof(Verbatim.CustomConverters.IConverter);
            Type t4 = this.MappingModel.FindType(item.Cast);
            if (t4 == null)
                return;

            Type[] t2 = t4.GetInterfaces();

            foreach (Type type in t2.Where(t => t.IsGenericType))
            {

                Type[] tb = type.GetGenericArguments();

                if (tb.Length < 2)
                    continue;

                string s = ItemMap.FindTypeName(tb[0]);
                string t = ItemMap.FindTypeName(tb[1]);

                if (s == item.SourceType && t == item.TargetType)
                {
                    item.CastWay = "Convert";
                    return;
                }
                if (s == item.TargetType && t == item.SourceType)
                {
                    item.CastWay = "ConvertBack";
                    return;
                }

                
            }

            context.LogError("the cast is not used to the types", "1", item);

        }
        */

        private void ValidateTypeWithCastIValueconverter(ValidationContext context, string typeSource, string typeTarget, string typeCast, ItemMap item)
        {

            Type t1 = this.MappingModel.FindType(typeSource);
            Type t2 = this.MappingModel.FindType(typeTarget);
            Type t3 = this.MappingModel.FindType(typeCast);

            if (t1 == null)
                context.LogError(string.Format("the type {0} for {1} can't be resolve", typeSource, "source"), "1", item);

            else if (t2 == null)
                context.LogError(string.Format("the type {0} for {1} can't be resolve", typeTarget, "destination"), "1", item);

            else if (t3 == null)
                context.LogError(string.Format("the type {0} for {1} can't be resolve", typeCast, "property Cast"), "1", item);

            else
            {
                ValidateTypeWithCastIValueconverter(context, t1, t2, t3, item);
            }

        }

        private void ValidateTypeWithCastIValueconverter(ValidationContext context, Type typeSource, Type typeTarget, Type typeCast, ItemMap item)
        {

            ValueConversionAttribute attr = (ValueConversionAttribute)Attribute.GetCustomAttribute(typeCast, typeof(ValueConversionAttribute));

            if (attr != null)
            {
                if (attr.SourceType != typeSource && attr.SourceType != typeTarget)                
                    context.LogError(string.Format("the converter {0} isn't valid for the this mapping ", typeCast.Name), "2", item);
                
                if (attr.TargetType != typeSource && attr.TargetType != typeTarget)                
                    context.LogError(string.Format("the converter {0} isn't valid for the this mapping ", typeCast.Name), "2", item);
                
            }

        }

        private void ValidateTypeWithCastExplicit(ValidationContext context, ItemMap item)
        {
            context.LogMessage("The element uses an explicit cast, which can cause unexpected errors", "2", item);
        }
        
        #endregion

        #endregion ItemMap

        
    }
}